#pragma once

#include "vec2.h"
#include <math.h>

OPEN_JLIB_NS


struct alignas(16) Vec3 {

  f32 p[3], _;

  inline Vec3 () noexcept = default;
  inline Vec3 (f32 x, f32 y, f32 z = 0.0F) noexcept {
    p[0] = x;
    p[1] = y;
    p[2] = z;
  }
  inline Vec3 (Vec2 v, f32 z = 0.0F) noexcept {
    p[0] = v.p[0];
    p[1] = v.p[1];
    p[2] = z;
  }
  inline Vec3 (f32 s) noexcept {
    p[0] = s;
    p[1] = s;
    p[2] = s;
  }
  inline Vec3 (const f32* m) noexcept {
    p[0] = m[0];
    p[1] = m[1];
    p[2] = m[2];
  }


  inline f32 operator[] (uwl i) const noexcept {
    return p[i];
  }


  inline f32 x () const noexcept {
    return p[0];
  }
  inline f32 y () const noexcept {
    return p[1];
  }
  inline f32 z () const noexcept {
    return p[2];
  }


  inline Vec3& x (f32 s) noexcept {
    p[0] = s;
    return *this;
  }
  inline Vec3& y (f32 s) noexcept {
    p[1] = s;
    return *this;
  }
  inline Vec3& z (f32 s) noexcept {
    p[2] = s;
    return *this;
  }


  inline Vec3& operator+= (Vec3 v) noexcept {
    p[0] += v.p[0];
    p[1] += v.p[1];
    p[2] += v.p[2];
    return *this;
  }
  inline Vec3& operator-= (Vec3 v) noexcept {
    p[0] -= v.p[0];
    p[1] -= v.p[1];
    p[2] -= v.p[2];
    return *this;
  }
  inline Vec3& operator*= (Vec3 v) noexcept {
    p[0] *= v.p[0];
    p[1] *= v.p[1];
    p[2] *= v.p[2];
    return *this;
  }
  inline Vec3& operator/= (Vec3 v) noexcept {
    p[0] /= v.p[0];
    p[1] /= v.p[1];
    p[2] /= v.p[2];
    return *this;
  }


  inline Vec3& operator*= (f32 s) noexcept {
    p[0] *= s;
    p[1] *= s;
    p[2] *= s;
    return *this;
  }
  inline Vec3& operator/= (f32 s) noexcept {
    p[0] /= s;
    p[1] /= s;
    p[2] /= s;
    return *this;
  }


  inline Vec3& clamp (Vec3 min, Vec3 max) noexcept {
    p[0] = fminf(fmaxf(p[0], min.p[0]), max.p[0]);
    p[1] = fminf(fmaxf(p[1], min.p[1]), max.p[1]);
    p[2] = fminf(fmaxf(p[2], min.p[2]), max.p[2]);
    return *this;
  }
  inline f32 normalize () noexcept {
    auto l = sqrtf(p[0]*p[0] + p[1]*p[1] + p[2]*p[2]);
    p[0] /= l;
    p[1] /= l;
    p[2] /= l;
    return l;
  }


  inline Vec3 operator+ (Vec3 v) const noexcept {
    return {p[0]+v.p[0], p[1]+v.p[1], p[2]+v.p[2]};
  }
  inline Vec3 operator- (Vec3 v) const noexcept {
    return {p[0]-v.p[0], p[1]-v.p[1], p[2]-v.p[2]};
  }
  inline Vec3 operator* (Vec3 v) const noexcept {
    return {p[0]*v.p[0], p[1]*v.p[1], p[2]*v.p[2]};
  }
  inline Vec3 operator/ (Vec3 v) const noexcept {
    return {p[0]/v.p[0], p[1]/v.p[1], p[2]/v.p[2]};
  }


  inline Vec3 operator* (f32 s) const noexcept {
    return {p[0]*s, p[1]*s, p[2]*s};
  }
  inline Vec3 operator/ (f32 s) const noexcept {
    return {p[0]/s, p[1]/s, p[2]/s};
  }


  inline f32 operator& (Vec3 v) const noexcept {
    return p[0]*v.p[0] + p[1]*v.p[1] + p[2]*v.p[2];
  }
  inline Vec3 operator^ (Vec3 v) const noexcept {
    return {
      p[1]*v.p[2] - p[2]*v.p[1],
      p[2]*v.p[0] - p[0]*v.p[2],
      p[0]*v.p[1] - p[1]*v.p[0],
    };
  }


  inline Vec3 operator+ () const noexcept {
    return {fabsf(p[0]), fabsf(p[1]), fabsf(p[2])};
  }
  inline Vec3 operator- () const noexcept {
    return {-p[0], -p[1], -p[2]};
  }
  inline Vec3 operator* () const noexcept {
    auto l = sqrtf(p[0]*p[0] + p[1]*p[1] + p[2]*p[2]);
    return {p[0]/l, p[1]/l, p[2]/l};
  }
  inline f32 operator~ () const noexcept {
    return sqrtf(p[0]*p[0] + p[1]*p[1] + p[2]*p[2]);
  }


  inline Vec3 luminance () const noexcept {
    auto l = 0.299F*p[0] + 0.587F*p[1] + 0.114F*p[2];
    return {l, l, l};
  }


  inline static Vec3 zero () noexcept {
    return {0.0F, 0.0F, 0.0F};
  }
  inline static Vec3 one () noexcept {
    return {1.0F, 1.0F, 1.0F};
  }
  template<u8 Com>
  inline static Vec3 unit () noexcept;


  inline static f32 distance (Vec3 a, Vec3 b) noexcept {
    return ~(b-a);
  }
  inline static Vec3 min (Vec3 a, Vec3 b) noexcept {
    return {
      fminf(a.p[0], b.p[0]),
      fminf(a.p[1], b.p[1]),
      fminf(a.p[2], b.p[2]),
    };
  }
  inline static Vec3 max (Vec3 a, Vec3 b) noexcept {
    return {
      fmaxf(a.p[0], b.p[0]),
      fmaxf(a.p[1], b.p[1]),
      fmaxf(a.p[2], b.p[2]),
    };
  }
  inline static Vec3 clamp (Vec3 v, Vec3 min, Vec3 max) noexcept {
    return Vec3::min(Vec3::max(v, min), max);
  }
  inline static Vec3 lerp (Vec3 a, Vec3 b, Vec3 t) noexcept {
    return a + (b-a)*t;
  }
  inline static Vec3 slerp (Vec3 a, Vec3 b, f32 t) noexcept {
    auto La = a.normalize();
    auto Lb = b.normalize();
    auto Lt = La + (Lb-La)*t;
    auto Ln = ~(a^b);
    auto R = asinf(Ln);
    auto r = R * t;
    auto p = sinf(R-r) / Ln;
    auto q = sinf(r) / Ln;
    return (a*p + b*q) * Lt;
  }
};


template<>
inline Vec3 Vec3::unit<0> () noexcept {
  return {1.0F, 0.0F, 0.0F};
}
template<>
inline Vec3 Vec3::unit<1> () noexcept {
  return {0.0F, 1.0F, 0.0F};
}
template<>
inline Vec3 Vec3::unit<2> () noexcept {
  return {0.0F, 0.0F, 1.0F};
}


inline Vec3 operator* (f32 a, Vec3 b) noexcept {
  return b * a;
}


inline Vec3 operator"" _RGB (unsigned long long c) noexcept {
  auto i = (u32)c;
  auto s = 1.0F / 255.0F;
  auto r = ((i >> 16) & 0xFF) * s;
  auto g = ((i >> 8) & 0xFF) * s;
  auto b = (i & 0xFF) * s;
  return {r, g, b};
}

inline Vec3 operator"" _LUM (unsigned long long c) noexcept {
  auto v = ((u32)c & 0xFF) * (1.0F / 255.0F);
  return {v, v, v};
}

inline Vec3 operator"" _YUV (unsigned long long c) noexcept {
  auto i = (u32)c;
  auto s = 1.0F / 255.0F;
  auto y = ((i >> 16) & 0xFF) * s;
  auto u = ((i >> 8) & 0xFF) * s;
  auto v = (i & 0xFF) * s;
  auto r = y + 1.14F * v;
  auto g = y - 0.39F * u - 0.58F * v;
  auto b = y + 2.03F * u;
  return {r, g, b};
}

inline Vec3 operator"" _HSV (unsigned long long c) noexcept {
  auto v = (c % 100) * 0.01F;
  auto s = (c / 100 % 100) * 0.01F;
  auto h = (c / 10000 % 360) * (1.0F / 60.0F);
  auto i = (s32)h;
  auto f = h - i;
  auto x = v * (1.0F - s);
  auto y = v * (1.0F - s * f);
  auto z = v * (1.0F - s * (1.0F - f));
  auto r = 0.0F;
  auto g = 0.0F;
  auto b = 0.0F;
  switch (i) {
    case 0: r = v; g = z; b = x; break;
    case 1: r = y; g = v; b = x; break;
    case 2: r = x; g = v; b = z; break;
    case 3: r = x; g = y; b = v; break;
    case 4: r = z; g = x; b = v; break;
    case 5: r = v; g = x; b = y; break;
    default: break;
  }
  return {r, g, b};
}


CLOSE_JLIB_NS
